/* bzflag
 * Copyright (c) 1993-2020 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// class interface header

#include "cURLManager.h"

// system includes
#include <string.h>

// common implementation headers
#include "bzfio.h"
#include "Protocol.h"
#include "TextUtils.h"

bool        cURLManager::inited = false;
bool        cURLManager::justCalled;
CURLM       *cURLManager::multiHandle = NULL;
std::map<CURL*,
    cURLManager*>  cURLManager::cURLMap;
char        cURLManager::errorBuffer[CURL_ERROR_SIZE];

cURLManager::cURLManager()
{
    CURLcode result;

    theData   = NULL;
    theLen    = 0;
    added     = false;

    formPost  = NULL;
    formLast  = NULL;

    if (!inited)
        setup();

    easyHandle = curl_easy_init();
    if (!easyHandle)
    {
        logDebugMessage(1,"Something wrong with CURL\n");
        return;
    }

    if (debugLevel >= 4)
    {
        result = curl_easy_setopt(easyHandle, CURLOPT_VERBOSE, (long)1);
        if (result != CURLE_OK)
            logDebugMessage(1,"CURLOPT_VERBOSE error: %d\n", result);
    }

    result = curl_easy_setopt(easyHandle, CURLOPT_ERRORBUFFER, errorBuffer);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_ERRORBUFFER error: %d\n", result);

    result = curl_easy_setopt(easyHandle, CURLOPT_FORBID_REUSE, (long)1);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_FORBID_REUSE error: %d\n", result);

    result = curl_easy_setopt(easyHandle, CURLOPT_NOSIGNAL, true);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_NOSIGNAL error %d : %s\n", result, errorBuffer);

    result = curl_easy_setopt(easyHandle, CURLOPT_FOLLOWLOCATION, true);
    if (result != CURLE_OK)
        logDebugMessage(1, "CURLOPT_FOLLOWLOCATION error %d : %s\n", result, errorBuffer);

    result = curl_easy_setopt(easyHandle, CURLOPT_WRITEFUNCTION,
                              cURLManager::writeFunction);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_WRITEFUNCTION error %d : %s\n", result, errorBuffer);

    result = curl_easy_setopt(easyHandle, CURLOPT_WRITEDATA, this);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_WRITEDATA error %d : %s\n", result, errorBuffer);

    cURLMap[easyHandle] = this;
}

cURLManager::~cURLManager()
{
    if (added)
        removeHandle();
    cURLMap.erase(easyHandle);
    curl_easy_cleanup(easyHandle);
    free(theData);
}

void cURLManager::setup()
{
    CURLcode result;

    logDebugMessage(1,"LIBCURL version:  %s\n", curl_version());
    if ((result = curl_global_init(CURL_GLOBAL_DEFAULT)))
        logDebugMessage(1,"cURL Global init Error: %d\n", result);
    /* might need to call removeHandle() still if added */
    if (!multiHandle)
        multiHandle = curl_multi_init();
    if (!multiHandle)
        logDebugMessage(1,"Unexpected error creating multi handle from libcurl \n");
    inited = true;
}

size_t cURLManager::writeFunction(void *ptr, size_t size, size_t nmemb,
                                  void *stream)
{
    int len = size * nmemb;
    ((cURLManager*)stream)->collectData((char*)ptr, len);
    justCalled = true;
    return len;
}

void cURLManager::setTimeout(long timeout)
{
    CURLcode result;

    result = curl_easy_setopt(easyHandle, CURLOPT_CONNECTTIMEOUT, timeout);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_CONNECTTIMEOUT error %d : %s\n", result, errorBuffer);
    // anything under 1Kbyte/sec is junk
    result = curl_easy_setopt(easyHandle, CURLOPT_LOW_SPEED_LIMIT, 1024);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_LOW_SPEED_LIMIT error %d : %s\n", result, errorBuffer);
    result = curl_easy_setopt(easyHandle, CURLOPT_LOW_SPEED_TIME, timeout);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_LOW_SPEED_TIME error %d : %s\n", result, errorBuffer);
}

void cURLManager::setNoBody()
{
    CURLcode result;
    long     nobody = 1;

    result = curl_easy_setopt(easyHandle, CURLOPT_NOBODY, nobody);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_NOBODY error %d : %s\n", result, errorBuffer);
}

void cURLManager::setGetMode()
{
    CURLcode result;
    long     get = 1;

    result = curl_easy_setopt(easyHandle, CURLOPT_HTTPGET, get);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_GET error %d : %s\n", result, errorBuffer);
}

void cURLManager::setPostMode(std::string _postData)
{
    CURLcode result;

    postData = _postData;

    result = curl_easy_setopt(easyHandle, CURLOPT_POSTFIELDS, postData.c_str());
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_POSTFIELD error %d : %s\n", result, errorBuffer);
    result = curl_easy_setopt(easyHandle, CURLOPT_POSTFIELDSIZE, postData.size());
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_POSTFIELD error %d : %s\n", result, errorBuffer);
    result = curl_easy_setopt(easyHandle, CURLOPT_POST, 1);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_POST error %d : %s\n", result, errorBuffer);
}

void cURLManager::setHTTPPostMode()
{
    CURLcode result;

    result = curl_easy_setopt(easyHandle, CURLOPT_HTTPPOST, formPost);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_HTTPPOST error %d : %s\n", result, errorBuffer);
}

void cURLManager::setURL(const std::string &url)
{
    CURLcode result;

    logDebugMessage(4,"Tried to fetch URL: %s\n", url.c_str());

    if (url == "")
        result = curl_easy_setopt(easyHandle, CURLOPT_URL, NULL);
    else
    {
        usedUrl = url;
        result = curl_easy_setopt(easyHandle, CURLOPT_URL, usedUrl.c_str());
        logDebugMessage(2,"CURLOPT_URL is : %s\n", usedUrl.c_str());
    }
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_URL error %d : %s\n", result, errorBuffer);
}

void cURLManager::setURLwithNonce(const std::string &url)
{
    // only the default list server is known to support the nonce parameter
    const std::string nonce = (strcasecmp(url.c_str(), DefaultListServerURL) == 0) ? TextUtils::format("?nocache=%lu",
                              time(0)) : "";
    setURL(url + nonce);
}

void cURLManager::setProgressFunction(curl_progress_callback func, const void* data)
{
    CURLcode result;
    if (func != NULL)
    {
        result = curl_easy_setopt(easyHandle, CURLOPT_PROGRESSFUNCTION, func);
        if (result == CURLE_OK)
            result = curl_easy_setopt(easyHandle, CURLOPT_PROGRESSDATA, data);
        if (result == CURLE_OK)
            result = curl_easy_setopt(easyHandle, CURLOPT_NOPROGRESS, 0);
    }
    else
        result = curl_easy_setopt(easyHandle, CURLOPT_NOPROGRESS, 1);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_SET_PROGRESS error %d : %s\n", result, errorBuffer);
}

void cURLManager::setRequestFileTime(bool request)
{
    CURLcode result;
    long     requestFileTime = request ? 1 : 0;
    result = curl_easy_setopt(easyHandle, CURLOPT_FILETIME, requestFileTime);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_FILETIME error %d : %s\n", result, errorBuffer);
}

void cURLManager::addHandle()
{
    CURLMcode result = curl_multi_add_handle(multiHandle, easyHandle);
    if (result > CURLM_OK)
        logDebugMessage(1,"Error while adding easy handle from libcurl %d : %s\n",
                        result, errorBuffer);
    added = true;
}

void cURLManager::removeHandle()
{
    if (!added)
        return;
    CURLMcode result = curl_multi_remove_handle(multiHandle, easyHandle);
    if (result != CURLM_OK)
        logDebugMessage(1,"Error while removing easy handle from libcurl %d : %s\n",
                        result, errorBuffer);
    added = false;
}

void cURLManager::finalization(char *, unsigned int, bool)
{
}

void cURLManager::collectData(char* ptr, int len)
{
    unsigned char *newData = (unsigned char *)realloc(theData, theLen + len);
    if (!newData)
        logDebugMessage(1,"memory exhausted\n");
    else
    {
        memcpy(newData + theLen, ptr, len);
        theLen += len;
        theData = newData;
    }
}

void cURLManager::performWait()
{
    CURLcode result;
    result = curl_easy_perform(easyHandle);
    infoComplete(result);
}

int cURLManager::fdset(fd_set &read, fd_set &write)
{
    fd_set    exc;
    int       max_fd = -1;
    CURLMcode result;

    FD_ZERO(&exc);

    result = curl_multi_fdset(multiHandle, &read, &write, &exc, &max_fd);
    if (result != CURLM_OK)
        logDebugMessage(1,"Error while doing multi_fdset from libcurl %d : %s\n",
                        result, errorBuffer);
    return max_fd;
}

bool cURLManager::perform()
{
    if (!inited)
        setup();

    int activeTransfers = 0;
    CURLMcode result;
    justCalled = false;
    while (true)
    {
        result = curl_multi_perform(multiHandle, &activeTransfers);
        if (result != CURLM_CALL_MULTI_PERFORM)
            break;
    }
    if (result != CURLM_OK)
        logDebugMessage(1,"Error while doing multi_perform from libcurl %d : %s\n",
                        result, errorBuffer);

    int      msgs_in_queue;
    CURLMsg *pendingMsg;
    CURL    *easy;

    while (true)
    {
        pendingMsg = curl_multi_info_read(multiHandle, &msgs_in_queue);
        if (!pendingMsg)
            break;

        easy  = pendingMsg->easy_handle;

        if (cURLMap.count(easy))
            cURLMap[easy]->infoComplete(pendingMsg->data.result);

        if (msgs_in_queue <= 0)
            break;
    }

    return justCalled;
}

void cURLManager::infoComplete(CURLcode result)
{
    if (result != CURLE_OK)
        logDebugMessage(1,"File transfer terminated with error from libcurl %d : %s\n",
                        result, errorBuffer);
    if (formPost)
    {
        curl_formfree(formPost);
        formPost = NULL;
        formLast = NULL;
    }
    removeHandle();
    finalization((char *)theData, theLen, result == CURLE_OK);
    justCalled = true;
    free(theData);
    theData = NULL;
    theLen  = 0;
}

bool cURLManager::getFileTime(time_t &t)
{
    long filetime;
    CURLcode result;
    result = curl_easy_getinfo(easyHandle, CURLINFO_FILETIME, &filetime);
    if (result)
    {
        logDebugMessage(1,"CURLINFO_FILETIME error %d : %s\n", result, errorBuffer);
        return false;
    }
    t = (time_t)filetime;
    return true;
}

bool cURLManager::getFileSize(double &size)
{
    CURLcode result;
    result = curl_easy_getinfo(easyHandle, CURLINFO_SIZE_DOWNLOAD, &size);
    if (result)
    {
        logDebugMessage(1,"CURLINFO_SIZE_DOWNLOAD error %d : %s\n", result, errorBuffer);
        return false;
    }
    return true;
}

void cURLManager::setTimeCondition(timeCondition condition, time_t &t)
{
    CURLcode result;

    switch (condition)
    {
    case None:
        result = curl_easy_setopt(easyHandle,
                                  CURLOPT_TIMECONDITION,
                                  CURL_TIMECOND_NONE);
        if (result != CURLE_OK)
            logDebugMessage(1,"CURLOPT_TIMECONDITION error %d : %s\n", result, errorBuffer);
        break;
    case ModifiedSince:
        result = curl_easy_setopt(easyHandle,
                                  CURLOPT_TIMECONDITION,
                                  CURL_TIMECOND_IFMODSINCE);
        if (result != CURLE_OK)
            logDebugMessage(1,"CURLOPT_TIMECONDITION error %d : %s\n", result, errorBuffer);
        result = curl_easy_setopt(easyHandle,
                                  CURLOPT_TIMEVALUE,
                                  (long)t);
        if (result != CURLE_OK)
            logDebugMessage(1,"CURLOPT_TIMEVALUE error %d : %s\n", result, errorBuffer);
        break;
    default:
        break;
    }
}

void cURLManager::setInterface(const std::string &_interfaceIP)
{
    interfaceIP = _interfaceIP;

    CURLcode result;

    result = curl_easy_setopt(easyHandle,
                              CURLOPT_INTERFACE,
                              interfaceIP.c_str());
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_SET_INTERFACE error %d : %s\n", result, errorBuffer);
}

void cURLManager::setUserAgent(const std::string &_userAgent)
{
    userAgent = _userAgent;

    CURLcode result;

    result = curl_easy_setopt(easyHandle,
                              CURLOPT_USERAGENT,
                              userAgent.c_str());
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_SET_USERAGENT error %d : %s\n", result, errorBuffer);
}

void cURLManager::addFormData(const char *key, const char *value)
{
    CURLFORMcode result;
    result = curl_formadd(&formPost, &formLast,
                          CURLFORM_COPYNAME, key,
                          CURLFORM_COPYCONTENTS, value,
                          CURLFORM_CONTENTSLENGTH, strlen(value),
                          CURLFORM_END);
    if (result != CURL_FORMADD_OK)
        logDebugMessage(1,"addFormData error %d : %s\n", result, errorBuffer);
}

void cURLManager::setDNSCachingTime(long time)
{
    CURLcode result;

    result = curl_easy_setopt(easyHandle,
                              CURLOPT_DNS_CACHE_TIMEOUT,
                              (long)time);
    if (result != CURLE_OK)
        logDebugMessage(1,"CURLOPT_SET_DNS_CACHE_TIMEOUT error %d : %s\n",
                        result, errorBuffer);
}

//**************************ResourceGetter*************************

ResourceGetter::ResourceGetter() : cURLManager()
{
    doingStuff = false;
}

ResourceGetter::~ResourceGetter()
{

}

void ResourceGetter::addResource ( trResourceItem &item )
{
    resources.push_back(item);

    if (!doingStuff)
        getResource();
}

void ResourceGetter::flush ( void )
{
    resources.clear();
    doingStuff = false;
}

void ResourceGetter::finalization(char *data, unsigned int length, bool good)
{
    if (!resources.size() || !doingStuff)
        return; // we are suposed to be done

    // this is who we are suposed to be geting
    trResourceItem item = resources[0];
    resources.erase(resources.begin());
    if (good)
    {
        // save the thing
        FILE *fp = fopen(item.filePath.c_str(),"wb");
        if (fp)
        {
            if (fwrite(data, length, 1, fp) != 1)
                logDebugMessage(1, "Unable to write to file with CURL\n");
            fclose(fp);
        }

        // maybe send a message here saying we did it?
    }

    // do the next one if we must
    getResource();
}

bool ResourceGetter::itemExists ( trResourceItem &item )
{
    // save the thing
    FILE *fp = fopen(item.filePath.c_str(),"rb");
    if (fp)
    {
        fclose(fp);
        return true;
    }
    return false;
}

void ResourceGetter::getResource ( void )
{
    while ( resources.size() && itemExists(resources[0]) )
        resources.erase(resources.begin());

    if ( !resources.size() )
        doingStuff = false;
    else
    {
        trResourceItem item = resources[0];

        doingStuff = true;
        setURL(item.URL);
        addHandle();
    }
}



// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
